import copy
import ast
from PySide2.QtWidgets import (
    QTableWidget,
    QVBoxLayout,
    QWidget,
    QSizePolicy,
    QComboBox,
    QHeaderView,
    QStyledItemDelegate,
    QTableWidgetItem,
    QLineEdit,
    QMenu,
    QApplication
)
from PySide2.QtCore import Signal, Qt, QEvent
from PySide2.QtGui import (
    QPainter,
    QPen,
    QColor,
    QDoubleValidator,
    QIntValidator,
    QCursor,
    QFocusEvent,
)
from PySide2.QtCore import QTimer
from functools import partial
import re
from PySide2 import QtWidgets, QtCore

# from ui_style import UiStyle
from . import message_box as MBox
from .utils import Utils
from .custom_widget import SelectBox, CustomCellComboBoxMultiSelectStr, SelectSearchBox



# class CustomComboBox(SelectBox):
#     # class CustomComboBox(SelectSearchBox):
#     popupHidden = Signal(str, str)
#     last_value = ""

#     def hidePopup(self):
#         super().hidePopup()  # 调用父类的 hidePopup 方法
#         QTimer.singleShot(100, self.get_current_value)  # 延迟获取当前选中项

#     def get_current_value(self):
#         value = self.currentText()
#         self.popupHidden.emit(self.last_value, value)  # 发出信号
#         self.last_value = value


# 下拉框代理
class ComboDelegate(QtWidgets.QStyledItemDelegate):
    sigUpdateModelData = Signal(
        QtCore.QModelIndex, object, object
    )  # 将 any 改为 object , PyQt5 中 any 是存在的,但是 PySide2 中没有,所以需要改为 object

    # 因为在 PySide2 中，Signal 的定义需要更明确地指定参数类型。我们需要修改 Signal 的定义，使用 object 类型而不是 any，因为 any 不是 Python 的类型提示，而是类型检查工具使用的特殊类型
    def __init__(self, parent=None):
        super().__init__(parent)
        self._type_config = {}
        self.options = []
        self.value_map = {}  # 值映射字典
        self.display_map = {}  # 显示值映射字典
        self.empty_value = ""
        self._is_setting_model_data = False  # 添加标志位
        self._current_index = None  # 记录当前编辑的索引
        self._old_value = None  # 记录旧值

    def set_config(self, type_config):
        """设置代理配置
        Args:
            type_config: 类型配置字典
        """
        self._type_config = type_config
        self.__init_vars()

    def on_init_cell(self, widgetItem: QtWidgets.QTableWidgetItem, value: any):  # type: ignore
        """初始化单元格,当前代理自己处理初始化时要显示的内容,以及决定是否要绑定数据,注意 set_header 是优先于 on_init_cell 调用的,因此可以放心使用已经初始化好的数据"""
        text = self.displayText(value, None)
        curShowText = "" if text is None else text
        widgetItem.setText(curShowText)
        if value is not None:
            widgetItem.setData(QtCore.Qt.UserRole, value)

    def __init_vars(self):
        """初始化变量"""
        options = []
        if "onInitOptions" in self._type_config and isinstance(
            self._type_config.get("onInitOptions"), type(lambda x: x)
        ):
            options = self._type_config.get("onInitOptions")() or []
        elif "options" in self._type_config:
            options = self._type_config.get("options", [])
        if not isinstance(options, list):
            options = []
        self.options = options

        hasEmptyOption = self._type_config.get(
            "hasEmptyOption", True
        )  # 缺省是True 表示有空选项
        if hasEmptyOption:
            self.empty_value = self._type_config.get("emptyValue", "")
        else:
            self.empty_value = None
        self.__init_maps(options)

    def __init_maps(self, options: list):
        """初始化映射"""
        self._value_map = {}
        self._display_map = {}
        for opt in options:
            value = opt.get("value", "")
            title = opt.get("title", "")
            self._value_map[value] = title
            self._display_map[title] = value

    def updateItems(self, options):
        """更新下拉框的选项"""
        self.options = options
        self.__init_maps(options)
        # 如果需要，可以在这里触发表格的刷新，以更新所有已打开的下拉框

    def createEditor(self, parent, option, index):
        editor = QtWidgets.QComboBox(parent)
        if self.empty_value is not None:
            editor.addItem("", self.empty_value)  # 添加空选项
        # 添加选项
        for opt in self.options:
            editor.addItem(opt.get("title", ""), opt.get("value", ""))
        # 记录当前编辑的索引和旧值
        self._current_index = index
        self._old_value = index.data(QtCore.Qt.UserRole)

        # 绑定信号：选项变化时立即触发
        editor.currentIndexChanged.connect(
            lambda: self._on_editor_value_changed(editor)
        )
        return editor

    def _on_editor_value_changed(self, editor):
        """编辑器值变化时的处理"""
        if self._current_index is None:
            return

        # 获取新值
        new_value = editor.currentData()

        # 立即发出信号（无需等待setModelData）
        self.sigUpdateModelData.emit(self._current_index, new_value, self._old_value)

    def setEditorData(self, editor, index):
        """设置编辑器数据

        Args:
            editor: 下拉框编辑器
            index: 模型索引
        """
        # 如果正在设置模型数据,则跳过
        if self._is_setting_model_data:
            return

        # 获取当前显示值
        display_value = index.model().data(index, QtCore.Qt.DisplayRole)
        # 注意 此值可能是实际值也可能是显示值
        # 获取当前实际值
        actual_value = index.model().data(index, QtCore.Qt.UserRole)
        if actual_value is None and display_value is not None:
            actual_value = display_value
        editor.setCurrentIndex(0)
        # 优先使用显示值,如果没有则使用实际值对应的显示值
        if actual_value in self.value_map:
            # 如果实际值在映射中,说明是实际值
            for i in range(editor.count()):
                if editor.itemData(i) == actual_value:
                    editor.setCurrentIndex(i)
                    break
        else:
            # 如果实际值不在映射中,说明是显示值
            for i in range(editor.count()):
                if editor.itemText(i) == display_value:
                    editor.setCurrentIndex(i)
                    break
        # 直接展开下拉框
        editor.showPopup()

    def setModelData(self, editor, model, index):
        """将编辑器数据保存到模型中
        Args:
            editor: 下拉框编辑器
            model: 数据模型
            index: 模型索引
        """
        # 设置标志位  解决  model.setData 会触发 setEditorData 的情况
        self._is_setting_model_data = True
        try:
            # 获取旧值（修改前的值）
            old_value = model.data(index, QtCore.Qt.UserRole)  # 从模型中读取旧的实际值
            # old_value = index.model().data(index, QtCore.Qt.UserRole)

            # 保存实际值
            model.setData(
                index, editor.itemData(editor.currentIndex()), QtCore.Qt.UserRole
            )
            # 保存显示值
            model.setData(index, editor.currentText(), QtCore.Qt.DisplayRole)

            # 更新缓存数据cls
            # value = index.model().data(index, QtCore.Qt.UserRole)
            # print(value, old_value)
            # self.sigUpdateModelData.emit(index, value, old_value)
        finally:
            # 确保标志位被重置
            self._is_setting_model_data = False

    def displayText(self, value, locale):
        """重写显示文本方法,用于在单元格中显示映射后的文本

        Args:
            value: 实际值
        Returns:
            str: 显示文本
        """
        if value is None or value == self.empty_value:
            return ""
        # 如果value是实际值,返回对应的显示值
        elif value in self.value_map:
            return self.value_map.get(value, "")
        # 如果value是显示值,直接返回
        elif value in self.display_map:
            return value
        else:
            return str(value)


class FloatValidatorDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        super(FloatValidatorDelegate, self).__init__(parent)
        self.validator = QDoubleValidator(self)
        self.validator.setNotation(QDoubleValidator.StandardNotation)

    def createEditor(self, parent, option, index):
        editor = QLineEdit(parent)  # 使用 QLineEdit 作为编辑器
        editor.setValidator(self.validator)
        return editor


class IntValidatorDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        super(IntValidatorDelegate, self).__init__(parent)
        self.validator = QIntValidator(self)

    def createEditor(self, parent, option, index):
        editor = QLineEdit(parent)  # 使用 QLineEdit 作为编辑器
        editor.setValidator(self.validator)
        return editor


class CustomTableWidgetItem(QTableWidgetItem):
    def __init__(self, text):
        super().__init__(text)
        self.setTextAlignment(Qt.AlignCenter)  # 文本居中对齐
        self._isReadyOnly = False

    # 重写排序规则：比较数字大小 非数字放在后面
    def __lt__(self, other):
        try:
            return int(self.text()) < int(other.text())
        except ValueError:
            return False

    def setReadyOnly(self, isReadyOnly):
        self._isReadyOnly = isReadyOnly

    def getReadyOnly(self):
        return self._isReadyOnly


# 新增行必须使用 addRow()
class ParamsTable(QTableWidget):
    select_changed_signal = Signal(QtCore.QModelIndex, object, object)
    item_changed_signal = Signal(object)
    custom_double_clicked_signal = Signal(object, int, int)

    def __init__(
        self, rows=0, columns=2, isLibrary=False, parent=None, isNoAddAndDel=False
    ):
        super(ParamsTable, self).__init__(parent)
        self.isLibrary = isLibrary
        self.isAddIncrementing = False
        self.combo_box = None
        self.rowFloat = []
        self.rowInt = []
        self.rowList = []
        self.rowSelect = []
        self.rowFilterSelect = []
        self.rowMultiSelect = []
        self.rowDoubleClick = []
        self.selectOptions = {}
        self.selectMultiOptions = {}
        self.isHandleDel = False
        self.noEditHint = "该单元格不可编辑"
        self.noDelHint = "该行不可删除"
        self.hasNoEditCell = False
        self.uniqueFlagColumn = -1
        self.current_edit_row = -1
        self.current_edit_col = -1
        self.incorrectFormatRowList = []  # 保存格式错误的行，用于导入和粘贴
        self.setRowCount(0)
        self.setColumnCount(columns)
        self.setAddRowCount(rows)
        # self.setColumnCount(columns)
        self.verticalHeader().setDefaultSectionSize(30)
        self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        # 不高亮标题
        self.horizontalHeader().setHighlightSections(False)
        #  # 垂直分割宽度
        self.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
        self.verticalHeader().setVisible(False)

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        # 创建QMenu信号事件
        self.customContextMenuRequested.connect(self.showMenu)
        self.contextMenu = QMenu(self)
        if not isNoAddAndDel:
            self.AD = self.contextMenu.addAction("新增")
            self.DL = self.contextMenu.addAction("删除")
            self.AD.triggered.connect(self.add)
            self.DL.triggered.connect(self.delete)

        self.CP = self.contextMenu.addAction("复制")
        self.NT = self.contextMenu.addAction("粘贴")
        self.EX = self.contextMenu.addAction("导出")
        self.CP.triggered.connect(self.copy)
        # self.JQ.triggered.connect(self.cut)
        self.NT.triggered.connect(self.paste)
        self.EX.triggered.connect(self.export)

        self.cellDoubleClicked.connect(self.on_cell_double_clicked)
        self.cellChanged.connect(self.nameCellChange)

    def showMenu(self, pos):
        # 取消当前选中的列
        self.clearSelection()
        item = self.itemAt(pos)
        if item:
            row = item.row()
            column = item.column()
            self.setCurrentCell(row, column)  # 选中当前单元格

        self.contextMenu.exec_(QCursor.pos())  # 在鼠标位置显示

    def keyPressEvent(self, event):  # 重写键盘监听事件
        # 监听 CTRL+C 组合键，实现复制数据到粘贴板
        if (
            event.key() == Qt.Key_C
        ) and QApplication.keyboardModifiers() == Qt.ControlModifier:
            self.copy()  # 获取当前表格选中的数据
        elif (
            event.key() == Qt.Key_X
        ) and QApplication.keyboardModifiers() == Qt.ControlModifier:
            self.cut()
        elif (
            event.key() == Qt.Key_V
        ) and QApplication.keyboardModifiers() == Qt.ControlModifier:
            
            if self.hasData():
                isPaste = MBox.confirm(
                    "即将粘贴数据到当前表格，可能会覆盖现有内容。是否继续？",
                )
                if isPaste:
                    self.paste()
            else:
                self.paste()
        # elif (event.key() == Qt.Key_Z) and QApplication.keyboardModifiers() == Qt.ControlModifier:
        #     self.paste()
        else:
            super().keyPressEvent(event)

    # 判断是否复制了多个单元格
    def isMutli(self, list):
        if len(list) == 0:
            return False
        if len(list) == 1:
            if len(list[0]) <= 1:
                return False
        return True

    def setAddRowCount(self, count):
        for i in range(count):
            self.addRow(i)

    # click event
    def add(self):
        lastRow = self.selectedLastRow()
        if lastRow < 0:
            lastRow = -1
        elif lastRow == self.rowCount() - 1:
            lastRow = -1
        # if lastRow < 0:
        #     lastRow = self.rowCount()
        else:
            lastRow = lastRow + 1
        self.addRow(lastRow, isAddIncrementing=True)

    def delete(self):
        selected_indexes = self.selectedIndexes()
        if not selected_indexes:
            MBox.warning(
                "请选择要删除的行",
            )
            return
        if not self.isHandleDel:
            isDel = MBox.confirm(
                "是否确认删除当前选中行?",
            )
            if isDel:
                # 将索引按行号降序排序，这样我们可以从底部开始删除行
                selected_rows = sorted(
                    set(index.row() for index in selected_indexes), reverse=True
                )
                for row in selected_rows:
                    self.removeRow(row)
            return

        isNoEdit = False
        for selectRow in selected_indexes:
            item = self.item(selectRow.row(), self.uniqueFlagColumn)
            if item.getReadyOnly():
                isNoEdit = True

        if isNoEdit:
            isDel = MBox.confirm(
                "当前选中行包含被使用的材料，该材料无法删除，是否继续?",
            )
            if isDel:
                # 将索引按行号降序排序，这样我们可以从底部开始删除行
                selected_rows = sorted(
                    set(index.row() for index in selected_indexes), reverse=True
                )
                for row in selected_rows:
                    item = self.item(row, self.uniqueFlagColumn)
                    if item.getReadyOnly() == False:
                        self.removeRow(row)
        else:
            isDel = MBox.confirm(
                "是否确认删除当前选中行?",
            )
            if isDel:
                # 将索引按行号降序排序，这样我们可以从底部开始删除行
                selected_rows = sorted(
                    set(index.row() for index in selected_indexes), reverse=True
                )
                for row in selected_rows:
                    self.removeRow(row)

    def paste(self):
        self.paste_tb_text()
        
    def export(self):
        Utils.export_to_csv(self)

    def copy(self):
        text = self.selected_tb_text()  # 获取当前表格选中的数据
        if text:
            clipboard = QApplication.clipboard()
            clipboard.setText(text)

    def cut(self):
        self.copy()
        self.del_tb_text()

    def nameCellChange(self, row, col):
        if self.current_edit_row == -1 or self.current_edit_col == -1:
            return
        if col != self.uniqueFlagColumn:
            return
        if row == self.current_edit_row and col == self.current_edit_col:
            item = self.item(row, col)
            self.value_is_duplicate(col=col, curItem=item)
            self.current_edit_row = -1
            self.current_edit_col = -1

    # click event patch
    def del_tb_text(self):
        try:
            indexes = self.selectedIndexes()
            for index in indexes:
                row, column = index.row(), index.column()
                item = CustomTableWidgetItem()
                self.setItem(row, column, item)
        except BaseException as e:
            print(e)
            return

    def paste_tb_text(self):
        self.incorrectFormatRowList = []
        try:
            text = QApplication.clipboard().text()
            pasteList = text.split("\n")
            # pasteList = [item for item in pasteList if item and item.strip()]
            # 复制后，尾部会有一个空字符串数组，需要去掉
            if len(pasteList) > 0:
                if pasteList[-1] == "":
                    pasteList.pop(-1)
            pasteList2D = []
            for row in pasteList:
                pasteList2D.append(row.split("\t"))
                # pasteList2D.append(re.split(r"[\t]", row))
            rows = len(pasteList)
            isMutli = self.isMutli(pasteList2D)
            indexes = self.selectedIndexes()
            if indexes:
                # 选中单元后粘贴
                for index in indexes:
                    index = index
                    break
                r, c = index.row(), index.column()  # 拿到选中的第一个单元格
                residueColumn = self.columnCount() - c
                if residueColumn < len(pasteList2D[0]):
                    isPaste = MBox.confirm(
                        f"粘贴数据包含{len(pasteList2D[0])}列，但表格仅剩{residueColumn}列，超出部分不会被粘贴。是否继续？",
                    )
                    if isPaste:
                        self.seletedToPaste(
                            isMutli, rows, r, c, pasteList2D, indexes, text
                        )
                else:
                    self.seletedToPaste(isMutli, rows, r, c, pasteList2D, indexes, text)
            else:
                # 直接粘贴
                sameIdeDict = {}
                # # 判断数据是否有id
                # hasId = True
                # if Utils.toInt(pasteList2D[0][0]) == "":
                #     # 没有id
                #     hasId = False
                if self.uniqueFlagColumn != -1:
                    # if not hasId:
                    #     pasteList2D = [[""] + sublist for sublist in pasteList2D]
                    copyData = pasteList2D.copy()
                    tableDict = self.getTableDict()
                    for addIndex in range(len(copyData) - 1, -1, -1):  # 倒序遍历
                        csvRow = copyData[addIndex]

                        if (
                            csvRow[self.uniqueFlagColumn] is not None
                            and str(csvRow[self.uniqueFlagColumn]) in tableDict
                        ):
                            sameIdeDict[tableDict[str(csvRow[1])]["row"]] = csvRow
                            # self.updateRowData(
                            #     tableDict[str(csvRow[self.uniqueFlagColumn])]["row"],
                            #     csvRow,
                            # )
                            del pasteList2D[addIndex]
                    self.addMulitRows(
                        pasteList2D, isAddIncrementing=True, isPastOrImport=True
                    )
                    if len(sameIdeDict) > 0:
                        isIdSame = MBox.confirm(
                            text=f"目标包含{str(len(sameIdeDict))}个相同id的数据!",
                            title="替换或跳过",
                            sureText="替换",
                            cancelText="跳过",
                        )
                        if isIdSame:
                            for row, value in sameIdeDict.items():
                                self.updateRowData(row, value, True)
                else:
                    self.addMulitRows(pasteList2D, isPastOrImport=True)
            self.sortTableByID()
            if len(self.incorrectFormatRowList) > 0:
                MBox.warning(
                    text=f"数据格式不正确!",
                )
                self.incorrectFormatRowList = []
        except Exception as e:
            print(e)
            return

    def seletedToPaste(self, isMutli, rows, r, c, pasteList2D, indexes, text):
        text = text.replace("\n", "")
        if isMutli:
            # 粘贴多个
            if self.uniqueFlagColumn != -1:
                # 粘贴从第一列开始
                # if c == 0:
                #     copyData = pasteList2D.copy()
                #     tableDict = self.getTableDict()
                #     for addIndex in range(
                #         len(copyData) - 1, -1, -1
                #     ):  # 倒序遍历
                #         csvRow = copyData[addIndex]
                #         if (
                #             csvRow[self.uniqueFlagColumn] is not None
                #             and str(csvRow[self.uniqueFlagColumn]) in tableDict
                #         ):
                #             self.updateRowData(
                #                 tableDict[str(csvRow[self.uniqueFlagColumn])][
                #                     "row"
                #                 ],
                #                 csvRow,
                #                 True,
                #             )
                #             del pasteList2D[addIndex]
                #     self.addMulitRows(pasteList2D, isPastOrImport=True)
                # else:
                # 粘贴不从第一列开始，暂时都使用这个
                for addRow in range(rows):
                    # 拿到真实的row
                    row = addRow + r
                    if row > self.rowCount() - 1:
                        # 新增行
                        rowData = [None] * self.columnCount()
                        rowData[c : c + len(pasteList2D[addRow])] = pasteList2D[addRow]
                        self.addRow(row, rowData, isPastOrImport=True)
                    else:
                        # 修改行
                        rowData = self.getRowData(row)
                        rowData[c : c + len(pasteList2D[addRow])] = pasteList2D[addRow]
                        self.fillRowData(row, rowData, True)
            else:
                # 第一列的值不唯一
                for addRow in range(rows):
                    # 拿到真实的row
                    row = addRow + r
                    if row > self.rowCount() - 1:
                        # 新增行
                        rowData = [None] * self.columnCount()
                        rowData[c : c + len(pasteList2D[addRow])] = pasteList2D[addRow]
                        self.addRow(row, rowData, isPastOrImport=True)
                    else:
                        # 修改行
                        rowData = self.getRowData(row)
                        rowData[c : c + len(pasteList2D[addRow])] = pasteList2D[addRow]
                        self.fillRowData(row, rowData, True)
        else:
            # 单个 复制单个单元或值，粘贴到所有选中的单元格中
            for index in indexes:
                # if (
                #     self.uniqueFlagColumn == index.column()
                #     or 0 == index.column()
                # ):
                #     # TODO这里暂时禁止复制，后续可继续优化处理
                #     continue
                self.updateCellData(
                    index.row(),
                    index.column(),
                    text,
                    isPastOrImport=True,
                )

    def selected_tb_text(self):
        try:
            indexes = self.selectedIndexes()  # 获取表格对象中被选中的数据索引列表
            indexes_dict = {}
            for index in indexes:  # 遍历每个单元格
                row, column = index.row(), index.column()  # 获取单元格的行号，列号
                if row in indexes_dict.keys():
                    indexes_dict[row].append(column)
                else:
                    indexes_dict[row] = [column]
            # 将数据表数据用制表符(\t)和换行符(\n)连接，使其可以复制到excel文件中
            text = ""
            for row, columns in indexes_dict.items():
                row_data = None
                for column in columns:
                    # if column in self.rowSelect:
                    #     wrapWidget = self.cellWidget(row, column)
                    #     wrapLayout = wrapWidget.layout()
                    #     selectBox = wrapLayout.itemAt(0).widget()
                    #     data = selectBox.getValue()
                    # elif column in self.rowMultiSelect:
                    #     wrapWidget = self.cellWidget(row, column)
                    #     data = ','.join(wrapWidget.getValue())
                    # else:
                    data = self.item(row, column).text()

                    if row_data is not None:
                        row_data = row_data + "\t" + data
                    else:
                        row_data = data

                if text:
                    text = text + "\n" + row_data
                else:
                    text = row_data if row_data is not None else ""
            return text
        except BaseException as e:
            print(e)
            return ""

    def selectChanged(self, *args, **kwargs):
        pass
        # self.select_changed_signal.emit(args)

    def setAddIncrementing(self, isAddIncrementing=True):
        self.isAddIncrementing = isAddIncrementing

    # column rules
    def rowSetInt(self, toInt):
        if isinstance(toInt, list):
            self.rowInt = toInt
            for row in toInt:
                self.setItemDelegateForColumn(row, IntValidatorDelegate(self))
        else:
            self.rowInt.append(toInt)
            self.setItemDelegateForColumn(toInt, IntValidatorDelegate(self))

    def rowSetFloat(self, toFloat):
        if isinstance(toFloat, list):
            self.rowFloat = toFloat
            for row in toFloat:
                self.setItemDelegateForColumn(row, FloatValidatorDelegate(self))
        else:
            self.rowFloat.append(toFloat)
            self.setItemDelegateForColumn(toFloat, FloatValidatorDelegate(self))

    def rowSetList(self, toList):
        if isinstance(toList, list):
            self.rowList = toList
        else:
            self.rowList.append(toList)

    def rowSetSelect(self, toSelect, options={}):
        self.selectOptions = options
        # if isinstance(toSelect, list):
        #     self.rowSelect = toSelect
        # else:
        #     self.rowSelect.append(toSelect)

        #  {
        #         "5": [
        #             {"value": "定位点", "title": "定位点"},
        #             {"value": "环框", "title": "环框"},
        #         ],
        #         "6": self.materialOptions,
        #         "7": self.stringedBarOptions,
        #     },

        for col, opt in options.items():
            delegate = ComboDelegate(self)
            delegate.set_config(
                {
                    "options": opt,
                    # "hasEmptyOption": True,
                    # "emptyValue": None
                }
            )
            self.setItemDelegateForColumn(int(col), delegate)
            delegate.sigUpdateModelData.connect(self.sigUpdateModelData)

    def sigUpdateModelData(self, index, value, oldValue):
        self.select_changed_signal.emit(index, value, oldValue)

    def rowUpDateSelect(self, toSelect, options={}):
        for col, opt in options.items():
            delegate = self.itemDelegateForColumn(col)
            if delegate:
                delegate.updateItems(opt)

    def rowSetFilterSelect(self, toSelect, options={}):
        self.selectOptions = options
        if isinstance(toSelect, list):
            self.rowFilterSelect = toSelect
        else:
            self.rowFilterSelect.append(toSelect)

    def rowSetMultiSelect(self, toSelect, options={}):
        self.selectMultiOptions = options
        if isinstance(toSelect, list):
            self.rowMultiSelect = toSelect
        else:
            self.rowMultiSelect.append(toSelect)

    def rowSetDoubleClick(self, toDoubleClick):
        if isinstance(toDoubleClick, list):
            self.rowDoubleClick = toDoubleClick
        else:
            self.rowDoubleClick.append(toDoubleClick)

    def getRuleValueToStr(self, value, column):
        if column in self.rowFloat:
            return str(Utils.toFloat(value))
        elif column in self.rowInt:
            return str(Utils.toInt(value))
        else:
            return str(value) if value else ""

    def getRuleValue(self, value, column):
        if column in self.rowFloat:
            return Utils.toFloatOrNone(value)
        elif column in self.rowInt:
            return Utils.toIntOrNone(value)
        else:
            return str(value) if value else ""

    def on_cell_double_clicked(self, row, column):
        self.current_edit_row = row
        self.current_edit_col = column
        # 双击是否允许编辑唯一值
        # if column == self.uniqueFlagColumn:
        #     item = self.item(row, column)
        #     if item:
        #         if item.getReadyOnly():
        #             MBox.warning(self.noEditHint)
        # if column in self.rowSelect:
        #     opt = self.selectOptions[str(column)]
        #     combo = CustomComboBox(
        #         options=opt,
        #         flag=[row, column],
        #         onChanged=lambda *args, **kwargs: self.selectChanged(
        #             self, *args, **kwargs
        #         ),
        #     )
        #     combo.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Expanding)
        #     combo.popupHidden.connect(partial(self.remove_combo, row=row, col=column))
        #     item = self.item(row, column)
        #     if item:
        #         combo.setValue(item.text())
        #         combo.last_value = item.text()
        #     self.setCellWidget(row, column, combo)
        #     combo.showPopup()
        # elif column in self.rowMultiSelect:
        #     opt = copy.deepcopy(self.selectMultiOptions[str(column)])
        #     multiSelect = CustomCellComboBoxMultiSelectStr(options=opt)
        #     multiSelect.multiSelectPopupHidden.connect(
        #         partial(self.remove_combo, row=row, col=column)
        #     )
        #     item = self.item(row, column)
        #     if item:
        #         multiSelect.setValue(item.text())
        #     self.setCellWidget(row, column, multiSelect)
        #     multiSelect.showPopup()
        if column in self.rowDoubleClick:
            item = self.item(row, column)
            self.custom_double_clicked_signal.emit(item.text(), row, column)

    def remove_combo(self, lastValue, value, row, col):
        # 赋值给item
        item = self.item(row, col)
        if item is not None:
            if value != "":
                item.setText(value)
        else:
            # 如果单元格没有项，则创建一个新的空项并设置
            if value != "":
                self.setItem(row, col, CustomTableWidgetItem(value))
        # 删除指定单元格中的 QComboBox
        combo = self.cellWidget(row, col)
        if combo:
            combo.deleteLater()  # 安全删除
            self.removeCellWidget(row, col)

        # 传递信号
        self.select_changed_signal.emit(lastValue, value, row, col)

    # basic
    # 插入一行，可设置数据
    def addRow(self, row=-1, rowData=[], isAddIncrementing=False, isPastOrImport=False):
        if self.isAddIncrementing and isAddIncrementing:
            isAddIncrementing = True
        else:
            isAddIncrementing = False

        row_position = row
        if row < 0:
            row_position = self.rowCount()
        self.insertRow(row_position)
        previousItem = None
        if row_position > 0:
            previousItem = self.item(row_position - 1, 0)
        for column in range(self.columnCount()):
            item = CustomTableWidgetItem("")
            if column in self.rowDoubleClick:
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
            # 判断是否有数据
            if rowData and not all(x is None or x == "" for x in rowData):
                if len(rowData) > column:
                    formatValue = self.getRuleValueToStr(rowData[column], column)
                    if column == 0:
                        # 判断是否自增id
                        if (
                            rowData[0] == None or rowData[0] == ""
                        ) and isAddIncrementing:
                            if previousItem:
                                previousId = Utils.toIntOrNone(previousItem.text())
                                if previousId is not None:
                                    item.setText(str(previousId + 1))
                            else:
                                item.setText("1")
                        else:
                            item.setText(formatValue)
                    else:
                        if item.getReadyOnly() == False:
                            item.setText(formatValue)
                    if (
                        isPastOrImport
                        and formatValue == ""
                        and not (not rowData[column] and rowData[column] != 0)
                    ):
                        self.incorrectFormatRowList.append(row_position)
            else:
                if isAddIncrementing:
                    if column == 0:
                        if previousItem:
                            previousId = Utils.toIntOrNone(previousItem.text())
                            # 判断上一个id是否为空并且不能是插入而是新增
                            if previousId is not None and row == -1:
                                item.setText(str(previousId + 1))
                        else:
                            item.setText("1")
            self.setItem(row_position, column, item)

    # 末尾插入多行，并设置数据
    def addMulitRows(self, rowsData=[], isAddIncrementing=False, isPastOrImport=False):
        for rowData in rowsData:
            self.addRow(
                rowData=rowData,
                isAddIncrementing=isAddIncrementing,
                isPastOrImport=isPastOrImport,
            )

    # 填充指定行数据
    def fillRowData(self, row, rowData, isPastOrImport=False):
        totalColumn = self.columnCount()
        if rowData and isinstance(rowData, list):
            for column, data in enumerate(rowData):
                if column >= totalColumn:
                    break
                item = self.item(row, column)

                formatValue = self.getRuleValueToStr(data, column)
                if item is None:
                    item = CustomTableWidgetItem()
                    if not (isPastOrImport and formatValue == ""):
                        item.setText(formatValue)
                    else:
                        item.setText("")
                    self.setItem(row, column, item)
                else:
                    if item.getReadyOnly() == False:
                        if not (isPastOrImport and formatValue == ""):
                            item.setText(formatValue)
                        else:
                            item.setText("")
                # 判断是否有格式错误
                if (
                    isPastOrImport
                    and formatValue == ""
                    and not (not data and data != 0)
                ):
                    self.incorrectFormatRowList.append(row)

    # 更新指定行的数据
    def updateRowData(self, row, rowData, isPastOrImport=False):
        for col in range(self.columnCount()):
            if len(rowData) > col:
                self.updateCellData(row, col, rowData[col], isPastOrImport)

    # 更新指定单元格的数据
    def updateCellData(self, row, col, value, isPastOrImport=False):
        item = self.item(row, col)

        formatValue = self.getRuleValueToStr(value, col)
        if item is None:
            item = CustomTableWidgetItem()
            if not (isPastOrImport and formatValue == ""):
                item.setText(formatValue)
            else:
                item.setText("")
            self.setItem(row, col, item)
        if item.getReadyOnly() == False:
            # if Utils.toIntOrNone(value) is None:
            #     return
            if not (isPastOrImport and formatValue == ""):
                item.setText(formatValue)
            else:
                item.setText("")

        # 判断是否有格式错误
        if isPastOrImport and formatValue == "" and not (not value and value != 0):
            self.incorrectFormatRowList.append(row)

    # 获取当前选中行的最后一行行号
    def selectedLastRow(self):
        last_row_number = -1
        selected_indexes = self.selectedIndexes()
        if selected_indexes:
            # 获取所有选中行的行号
            rows = [index.row() for index in selected_indexes]
            # 找到最后一行的行号
            last_row_number = max(rows)
        return last_row_number

    # 动态设置行数和列数
    def setCustomRowCount(self, row_count):
        self.setRowCount(row_count)
        # self.setColumnCount(column_count)
        # 为每个单元格设置自定义的 CustomTableWidgetItem
        for row in range(row_count):
            for col in range(self.columnCount()):
                item = self.item(row, col)
                if item is None:
                    custom_item = CustomTableWidgetItem("")
                    self.setItem(row, col, custom_item)

    def getRowData(self, row):
        dataList = []
        if row > self.rowCount() - 1:
            return None
        for col in range(self.columnCount()):
            item = self.item(row, col)
            if item is not None:
                if col in self.rowList:
                    try:
                        if not item.text():
                            dataList.append([])
                        else:
                            array = ast.literal_eval(item.text())
                            if isinstance(array, list):
                                dataList.append(array)
                            else:
                                dataList.append([])
                    except (ValueError, SyntaxError) as e:
                        dataList.append([])
                        print(f"解析错误: {e}")
                else:
                    dataList.append(self.getRuleValue(item.text(), col))
            else:
                dataList.append(None)
        return [] if all(x == "" for x in dataList) else dataList

    def getRowDataNone(self, row):
        dataList = []
        if row > self.rowCount() - 1:
            return None
        for col in range(self.columnCount()):
            item = self.item(row, col)
            if item is not None:
                if col in self.rowList:
                    try:
                        if not item.text():
                            dataList.append([])
                        else:
                            array = ast.literal_eval(
                                self.getRuleValue(item.text(), col)
                            )
                            if isinstance(array, list):
                                dataList.append(array)
                            else:
                                dataList.append([])
                    except (ValueError, SyntaxError) as e:
                        dataList.append([])
                        print(f"解析错误: {e}")
                else:
                    dataList.append(self.getRuleValue(item.text(), col))
            else:
                dataList.append(None)
        return [] if all(x == "" for x in dataList) else dataList

    def getColumnValues(self, column=0):
        columnList = []
        for row in range(self.rowCount()):
            item = self.item(row, column)  # 获取第 0 列的单元格
            if item is not None:
                columnList.append(self.getRuleValue(item.text(), column))
        return columnList

    # dict={value:row}
    def getColumnDict(self, column=0, curRow=-1):
        columnDict = {}
        for row in range(self.rowCount()):
            if row == curRow:
                continue
            item = self.item(row, column)  # 获取第 0 列的单元格
            if item is not None:
                value = self.getRuleValue(item.text(), column)
                columnDict[value] = row
        return columnDict

    def getTableData(self):
        data = []
        for row in range(self.rowCount()):
            data.append(self.getRowDataNone(row))
        return data

    def getTableDatatFirstNoNull(self):
        data = []
        dict = {}
        keyCol = 0 if self.uniqueFlagColumn == -1 else self.uniqueFlagColumn
        for row in range(self.rowCount()):
            rowData = self.getRowDataNone(row)
            if rowData:
                # 判断是否为空，空返回int
                if rowData[keyCol] == "" or rowData[keyCol] is None:
                    return row + 1
                # 判断是否重复，重复返回str
                if str(rowData[keyCol]) in dict:
                    return str(row + 1)
                dict[str(rowData[keyCol])] = ""
            data.append(rowData)
        return data
    
    # def getTableDatatFirstNoNull_str(self):
    #     data = []
    #     dict = {}
    #     keyCol = 0 if self.uniqueFlagColumn == -1 else self.uniqueFlagColumn
    #     for row in range(self.rowCount()):
    #         rowData = self.getRowDataNone_str(row)
    #         if rowData:
    #             # 判断是否为空，空返回int
    #             if rowData[keyCol] == "" or rowData[keyCol] is None:
    #                 return row + 1
    #             # 判断是否重复，重复返回str
    #             if str(rowData[keyCol]) in dict:
    #                 return str(row + 1)
    #             dict[str(rowData[keyCol])] = ""
    #         data.append(rowData)
    #     return data
    
    # def getRowDataNone_str(self, row):
    #     dataList = []
    #     if row > self.rowCount() - 1:
    #         return None
    #     for col in range(self.columnCount()):
    #         item = self.item(row, col)
    #         if item is not None:
    #             if col in self.rowList:
    #                 try:
    #                     if not item.text():
    #                         dataList.append([])
    #                     else:
    #                         array = ast.literal_eval(
    #                             self.getRuleValue(item.text(), col)
    #                         )
    #                         if isinstance(array, list):
    #                             dataList.append(array)
    #                         else:
    #                             dataList.append([])
    #                 except (ValueError, SyntaxError) as e:
    #                     dataList.append([])
    #                     print(f"解析错误: {e}")
    #             else:
    #                 if col == 6:
    #                     dataList.append(item.text())
    #                 else:
    #                     dataList.append(self.getRuleValue(item.text(), col))
    #         else:
    #             dataList.append(None)
    #     return [] if all(x == "" for x in dataList) else dataList

    def getTableDict(self):
        dict = {}
        keyCol = 0 if self.uniqueFlagColumn == -1 else self.uniqueFlagColumn
        for row in range(self.rowCount()):
            rowData = self.getRowDataNone(row)
            if rowData:
                if rowData[keyCol] == "" or rowData[keyCol] is None:
                    continue
                dict[str(rowData[keyCol])] = {"content": rowData, "row": row}
        return dict

    def getTableDictFirstNoNull(self):
        dict = {}
        keyCol = 0 if self.uniqueFlagColumn == -1 else self.uniqueFlagColumn
        for row in range(self.rowCount()):
            rowData = self.getRowDataNone(row)
            if rowData:
                # 判断是否为空，空返回int
                if rowData[keyCol] == "" or rowData[keyCol] is None:
                    return row + 1
                # 判断是否重复，重复返回str
                if str(rowData[keyCol]) in dict:
                    return str(row + 1)
                dict[str(rowData[keyCol])] = rowData
        return dict

    def getTableListFirstNoNull(self):
        list = []
        dict = {}
        keyCol = 0 if self.uniqueFlagColumn == -1 else self.uniqueFlagColumn
        for row in range(self.rowCount()):
            rowData = self.getRowDataNone(row)
            if rowData:
                # 判断是否为空，空返回int
                if rowData[keyCol] == "" or rowData[keyCol] is None:
                    return row + 1
                # 判断是否重复，重复返回str
                if str(rowData[keyCol]) in dict:
                    return str(row + 1)
                dict[str(rowData[keyCol])] = rowData
                list.append(rowData)
        # print(list)
        return list

    def getTableDictFirstNoNullByKeys(self, keys):
        dict = {}
        keyCol = 0 if self.uniqueFlagColumn == -1 else self.uniqueFlagColumn
        for row in range(self.rowCount()):
            rowData = self.getRowDataNone(row)
            if rowData:
                # 判断是否为空，空返回int
                if rowData[keyCol] == "" or rowData[keyCol] is None:
                    return row + 1
                # 判断是否重复，重复返回str
                if str(rowData[keyCol]) in dict:
                    return str(row + 1)
                rowDict = {}
                for i, key in enumerate(keys):
                    rowDict[key] = rowData[i]
                dict[str(rowData[keyCol])] = rowDict
        return dict

    def setAllColumnWidth(self, width):
        for column in range(self.columnCount()):
            self.setColumnWidth(column, width)

    # 清空表格数据
    def clearRow(self, row=-1):
        # keyCol = 0 if self.uniqueFlagColumn == -1 else self.uniqueFlagColumn
        if row >= 0:
            column_count = self.columnCount()
            # 遍历每一列
            for column in range(column_count):
                # 获取当前单元格的项
                item = self.item(row, column)
                if item is not None:
                    # 将单元格内容设置为空字符串
                    item.setText("")
                else:
                    # 如果单元格没有项，则创建一个新的空项并设置
                    self.setItem(row, column, CustomTableWidgetItem(""))
        else:
            if self.hasNoEditCell and self.isHandleDel:
                isDel = MBox.confirm(
                    "当前选中行包含被使用的材料，被使用的材料无法删除，是否继续?",
                )
                if isDel:
                    rowCount = self.rowCount()
                    for row in range(rowCount - 1, -1, -1):
                        item = self.item(row, self.uniqueFlagColumn)
                        if item.getReadyOnly() == False:
                            self.removeRow(row)
                        rowCount = rowCount - 1
            else:
                isDel = MBox.confirm(
                    "是否确认清空表格数据?",
                )
                if isDel:
                    self.setRowCount(0)

    def value_is_duplicate(self, col=0, curItem=None, hint="名称重复！"):
        isDeplicate = False
        if curItem is None or curItem.text().strip() == "":
            return
        for i in range(self.rowCount()):
            item = self.item(i, col)
            if item is not None and curItem is not item:
                if curItem.text().strip() == item.text().strip():
                    isDeplicate = True
                    curItem.setText("")
                    continue
        if isDeplicate:
            MBox.warning(hint)
            return True
        else:
            return False

    # 根据id重排表格数据
    def sortTableByID(self):
        self.sortByColumn(0, Qt.AscendingOrder)

    def getRowDataCheck(self, row):
        dataList = []
        if row > self.rowCount() - 1:
            return None
        for col in range(self.columnCount()):
            item = self.item(row, col)
            if item is not None:
                value = self.getRuleValue(item.text().strip(), col)
                if value == "" or value is None:
                    return None
                else:
                    dataList.append(value)
            else:
                return None
        return [] if all(x == "" for x in dataList) else dataList

    def validate_table_data(self):
        dict = {}
        keyCol = 0 if self.uniqueFlagColumn == -1 else self.uniqueFlagColumn
        for row in range(self.rowCount()):
            rowData = self.getRowDataCheck(row)
            if not rowData:
                return False
            else:
                # # 判断是否为空，空返回int
                # if rowData[keyCol] == "" or rowData[keyCol] is None:
                #     return row + 1
                # 判断是否重复，重复返回str
                if str(rowData[keyCol]) in dict:
                    return str(row + 1)

                dict[str(rowData[keyCol])] = rowData
        return dict

    def isExistValueAtRow(self, value, column):
        for row in range(self.rowCount()):
            item = self.item(row, column)
            if item and item.text() and isinstance(value, str):
                if value.strip() == item.text().strip():
                    return row
        return False
    
    def hasData(self) -> bool:
        """
        判断表格中是否有有效数据
        
        返回:
            bool: True表示有数据，False表示无数据
        """
        # 检查行数和列数
        if self.rowCount() == 0 or self.columnCount() == 0:
            return False
        
        # 遍历所有单元格检查内容
        for row in range(self.rowCount()):
            for col in range(self.columnCount()):
                item = self.item(row, col)
                if item is not None and item.text().strip():
                    return True
        
        return False


class OnlyOneRowTable(ParamsTable):
    def __init__(
        self, rows=0, columns=2, isLibrary=False, parent=None, isNoAddAndDel=True
    ):
        super(OnlyOneRowTable, self).__init__(
            rows, columns, isLibrary, parent, isNoAddAndDel
        )
        # self.contextMenu.clear()

    def paste_tb_text(self):
        self.incorrectFormatRowList = []
        try:
            text = QApplication.clipboard().text()
            pasteList = text.split("\n")
            # 复制后，尾部会有一个空字符串数组，需要去掉
            if len(pasteList) > 0:
                if pasteList[-1] == "":
                    pasteList.pop(-1)
            pasteList2D = []
            for row in pasteList:
                pasteList2D.append(row.split("\t"))
            if not pasteList2D:
                return
            # rows = len(pasteList)
            # isMutli = self.isMutli(pasteList2D)
            indexes = self.selectedIndexes()
            r = 0
            c = 0
            if indexes:
                # 选中单元后粘贴
                for index in indexes:
                    index = index
                    break
                r, c = index.row(), index.column()  # 拿到选中的第一个单元格
            residueRow = self.rowCount() - r
            residueColumn = self.columnCount() - c
            pasteRows = len(pasteList2D)
            if residueColumn < len(pasteList2D[0]) or residueRow < len(pasteList2D):
                isPaste = MBox.confirm(
                    f"粘贴数据包含{len(pasteList2D)}行{len(pasteList2D[0])}列，但表格仅剩{residueRow}行{residueColumn}列，超出部分不会被粘贴。是否继续？",
                )
                if isPaste:
                    for dataRow in range(pasteRows):
                        if dataRow >= residueRow:
                            break
                        row = dataRow + r
                        rowData = self.getRowData(row)
                        rowData[c : c + len(pasteList2D[dataRow])] = pasteList2D[
                            dataRow
                        ]
                        self.fillRowData(row, rowData, True)

            else:
                row = r
                rowData = self.getRowData(row)
                rowData[c : c + len(pasteList2D[0])] = pasteList2D[0]
                self.fillRowData(row, rowData, True)

            if len(self.incorrectFormatRowList) > 0:
                MBox.warning(
                    text=f"数据格式不正确!",
                )
                self.incorrectFormatRowList = []
        except Exception as e:
            print(e)
            return

    # 填充指定行数据
    def fillRowData(self, row, rowData, isPastOrImport=False):
        if not rowData:
            return
        totalColumn = self.columnCount()
        if rowData and isinstance(rowData, list):
            for column, data in enumerate(rowData):
                if column >= totalColumn:
                    break
                item = self.item(row, column)
                formatValue = self.getRuleValueToStr(data, column)
                if item is None:
                    item = QTableWidgetItem()
                    if not (isPastOrImport and formatValue == ""):
                        item.setText(formatValue)
                    else:
                        item.setText("")
                    self.setItem(row, column, item)
                else:
                    if item.getReadyOnly() == False:
                        if not (isPastOrImport and formatValue == ""):
                            item.setText(formatValue)
                        else:
                            item.setText("")
                # 判断是否有格式错误
                if (
                    isPastOrImport
                    and formatValue == ""
                    and not (not data and data != 0)
                ):
                    self.incorrectFormatRowList.append(row)

    def adjust_table_height(self, row_count=1):
        """
        调整 QTableWidget 的高度，使其正好显示指定行数（默认 2 行）
        :param table_widget: 要调整的 QTableWidget
        :param row_count: 要显示的行数
        """
        # 确保至少有 1 行用于计算行高
        if self.rowCount() == 0:
            self.setRowCount(1)

        # 计算单行高度（包括行间距）
        row_height = self.rowHeight(0)  # 获取第 0 行的高度

        # 计算表头高度（如果可见）
        header_height = 0
        if not self.horizontalHeader().isHidden():
            header_height = self.horizontalHeader().height()

        # 计算总高度（行高 × 行数 + 表头高度 + 边框）
        total_height = row_height * row_count + header_height + 2  # +2 是边框调整

        # 设置固定高度
        self.setFixedHeight(total_height)
        
    

    # def validate_table_data(self):
    #     """
    #     检查表格数据的完整性
    #     :param table_widget: QTableWidget实例
    #     :return: 如果数据完整返回True，否则返回False
    #     """
    #     row_count = self.rowCount()
    #     col_count = self.columnCount()

    #     # 检查每行每列的数据
    #     for row in range(row_count):
    #         for col in range(col_count):
    #             item = self.item(row, col)

    #             # 检查空单元格
    #             if item is None or item.text().strip() == "":
    #                 QMessageBox.warning(None, "警告",
    #                                 f"第{row+1}行, 第{col+1}列不能为空！")
    #                 return False

    #             # # 特定列的数据类型检查（示例：第2列必须为数字）
    #             # if col == 1:  # 假设第二列需要是数字
    #             #     try:
    #             #         float(item.text())
    #             #     except ValueError:
    #             #         QMessageBox.warning(None, "警告",
    #             #                         f"第{row+1}行, 第{col+1}列必须是数字！")
    #             #         return False
